extern crate rusqlite;
//extern crate chrono;
extern crate md5;

use std::sync::{Mutex, Arc};
use std::error::Error;
use std::fmt;

//----- type define -----

//#[derive(Clone)]
pub struct ShareState {
    pub connection: rusqlite::Connection,
    //pub tokens: Vec<String>
}


#[derive(Debug)]
pub struct SimpleError {
    pub desc: String
}

impl fmt::Display for SimpleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.desc)
    }
}

impl Error for SimpleError {
    fn description(&self) -> &str {
        &self.desc
    }
    fn cause(&self) -> Option<&Error> {
        Some(self)
    }
}

//----- database init -----

//type ArcConnection = Arc<Mutex<rusqlite::Connection>>;

pub fn open_database(path : &str) -> Result<rusqlite::Connection, Box<Error>> {
    let mut conct = rusqlite::Connection::open(path)?;
    let mut exist = false;

    {
        let mut query = conct.prepare("\
        SELECT * FROM sqlite_master
        WHERE type='table' AND name='dragonpak'
        ")?;

        exist = query.exists(&[])?;
    }


    if !exist {
        init_database(&mut conct)?;
    }


    Ok(conct)

}


fn init_database(conct: &mut rusqlite::Connection) -> Result<(), Box<Error>> {

    let db_init = "\
    PRAGMA foreign_keys = ON;
    PRAGMA encoding = \"UTF-8\";
    ";

    let create_user = "\
    CREATE TABLE user (
    id TEXT PRIMARY KEY,
    password TEXT
    )
    ";
    let create_article_class = "\
    CREATE TABLE article_class (
    name TEXT PRIMARY KEY
    )
    ";
    let create_article = "\
    CREATE TABLE article (
    id INTEGER PRIMARY KEY,
    title TEXT,
    date INTEGER,
    poster TEXT,
    intro TEXT,
    class TEXT,
    content TEXT,
    FOREIGN KEY(poster) REFERENCES user(id),
    FOREIGN KEY(class) REFERENCES article_class(name)
    )
    ";
    let create_app_class = "\
    CREATE TABLE app_class (
    name TEXT PRIMARY KEY
    )
    ";

    let create_app = "\
    CREATE TABLE app (
    package TEXT PRIMARY KEY,
    name TEXT,
    intro TEXT,
    icon TEXT,
    date INTEGER,
    poster TEXT,
    class TEXT,
    content TEXT,
    FOREIGN KEY(poster) REFERENCES user(id),
    FOREIGN KEY(class) REFERENCES app_class(name)
    )
    ";
    let create_dragonpak = "\
    CREATE TABLE dragonpak (
    key TEXT PRIMARY KEY,
    value TEXT
    )
    ";

    let create_token = "
    CREATE TABLE token (
    token TEXT PRIMARY KEY,
    info TEXT,
    timeout INTEGER
    )
    ";

    conct.execute_batch(db_init)?;
    conct.execute(create_user, &[])?;
    conct.execute(create_article_class, &[])?;
    conct.execute(create_article, &[])?;
    conct.execute(create_app_class, &[])?;
    conct.execute(create_app, &[])?;
    conct.execute(create_dragonpak, &[])?;
    conct.execute(create_token, &[])?;

    Ok(())
}

//----- token -----

//检查指定的token在数据库里是否存在且没超时
pub fn check_token(conct: &rusqlite::Connection, token: &str, timeout: i64) -> Option<String> {

    //use self::chrono::{offset::Local};
    //获取本地时间
    //let timeout: i64 = Local::now().timestamp();

    let query = format!(r#"
    SELECT token, info, timeout FROM token 
    WHERE token = "{}" AND timeout > {}
    "#, token, timeout);

    //let info: String = conct.query_row(&query, &[], |r| r.get(1))?;
    match conct.query_row(&query, &[], |r| r.get::<_, String>(1)) {
        Ok(i) => Some(i),
        Err(_) => None
    }

    //Err("dd")
}
//根据info添加一个token，成功则返回一个由info计算的md5值作token
pub fn add_token(conct: &rusqlite::Connection, info: &str, timeout: i64) -> Result<String, Box<Error>> {

    let md5_digest = md5::compute(info);
    let md5 = format!("{:x}", md5_digest);

    let query = format!(r#"
    INSERT INTO token VALUES (?, ?, ?)
    "#);

    let update_row = conct.execute(&query, &[&md5, &info, &timeout])?;

    if update_row == 0 {
        Err(SimpleError {desc: "insert failed".to_string()})?;
    }
    Ok(md5)
}

//清除数据库中所有超时时间小与timeout的token
pub fn clear_token(conct: &rusqlite::Connection, timeout: i64) -> Result<i32, Box<Error>> {

    let query = format!(r#"
    DELETE FROM token
    WHERE timeout <= ?
    "#);

    Ok(conct.execute(&query, &[&timeout])?)
}

//----- test -----

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn token_test(){
        let conct = open_database("data/db/test.db").unwrap();
        let info = r#"
        {
            "user":"wsgalaxy",
            "create_date":123456,
            "timeout":143456,
            "random":1254687
        }
        "#;
        let md5 = "d375fad26d94addd88b3ac3137062bcb";

        assert_eq!(check_token(&conct, md5, 123456), None);
        let new_md5 = add_token(&conct, &info, 143456).unwrap();
        assert_eq!(md5, new_md5);
        let new_info = check_token(&conct, md5, 123456);
        assert_eq!(new_info, Some(info.to_string()));
        println!("clear timeout 103555 :{}", clear_token(&conct, 103555).unwrap());
        println!("clear timeout 153555 :{}", clear_token(&conct, 153555).unwrap());
    }
}
